Skip to content

Commit

Permalink
fix(core): fix nested inlined embedded property hydration
Browse files Browse the repository at this point in the history
Closes #4145
  • Loading branch information
B4nan committed Mar 17, 2023
1 parent ee3e692 commit 2bbcb47
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 14 deletions.
28 changes: 20 additions & 8 deletions packages/core/src/hydration/ObjectHydrator.ts
Expand Up @@ -5,7 +5,6 @@ import { Reference } from '../entity/Reference';
import { Utils } from '../utils/Utils';
import { ReferenceType } from '../enums';
import type { EntityFactory } from '../entity/EntityFactory';
import { JsonType } from '../types/JsonType';

type EntityHydrator<T> = (entity: T, data: EntityData<T>, factory: EntityFactory, newEntity: boolean, convertCustomTypes: boolean, schema?: string) => void;

Expand Down Expand Up @@ -219,23 +218,36 @@ export class ObjectHydrator extends Hydrator {
}
};

const hydrateEmbedded = (prop: EntityProperty, path: string[], dataKey: string): string[] => {
const entityKey = path.map(k => this.wrap(k)).join('');
const ret: string[] = [];
const createCond = (prop: EntityProperty, dataKey: string) => {
const conds: string[] = [];
registerEmbeddedPrototype(prop, path);
parseObjectEmbeddable(prop, dataKey, ret);

if (prop.object) {
conds.push(`data${dataKey} != null`);
} else {
const notNull = prop.nullable ? '!= null' : '!== undefined';
meta.props
.filter(p => p.embedded?.[0] === prop.name)
.forEach(p => conds.push(`data${this.wrap(p.name)} ${notNull}`));
.forEach(p => {
if (p.reference === ReferenceType.EMBEDDED && !p.object && !p.array) {
conds.push(...createCond(p, dataKey + this.wrap(p.embedded![1])));
return;
}

conds.push(`data${this.wrap(p.name)} ${notNull}`);
});
}

ret.push(` if (${conds.join(' || ')}) {`);
return conds;
};

const hydrateEmbedded = (prop: EntityProperty, path: string[], dataKey: string): string[] => {
const entityKey = path.map(k => this.wrap(k)).join('');
const ret: string[] = [];

registerEmbeddedPrototype(prop, path);
parseObjectEmbeddable(prop, dataKey, ret);

ret.push(` if (${createCond(prop, dataKey).join(' || ')}) {`);

if (prop.targetMeta?.polymorphs) {
prop.targetMeta.polymorphs!.forEach(meta => {
Expand Down
91 changes: 91 additions & 0 deletions tests/features/embeddables/GH4145.test.ts
@@ -0,0 +1,91 @@
import { Embeddable, Embedded, Entity, PrimaryKey, Property, wrap } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/better-sqlite';

@Embeddable()
class Time {

@Property()
hour: number;

@Property()
minute: number;

constructor(hour: number, minute: number) {
this.hour = hour;
this.minute = minute;
}

}

@Embeddable()
class TimeInterval {

@Embedded(() => Time)
start!: Time;

@Embedded(() => Time)
end!: Time;

}

@Entity()
class Example {

@PrimaryKey()
name!: string;

@Embedded(() => TimeInterval)
timeInterval!: TimeInterval;

}

describe('embedded entities without other properties', () => {
let orm: MikroORM;

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

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

test('fetching entity should populate embedded property', async () => {
const example = new Example();
example.name = 'Test';
example.timeInterval = new TimeInterval();
example.timeInterval.start = new Time(9, 0);
example.timeInterval.end = new Time(17, 59);

expect(wrap(example.timeInterval).toJSON()).toEqual({
start: {
hour: 9,
minute: 0,
},
end: {
hour: 17,
minute: 59,
},
});

await orm.em.persistAndFlush(example);
orm.em.clear();

const fetched = await orm.em.findOneOrFail(Example, { name: example.name });
expect(fetched.timeInterval).not.toBeUndefined();
expect(wrap(fetched.timeInterval).toJSON()).toEqual({
start: {
hour: 9,
minute: 0,
},
end: {
hour: 17,
minute: 59,
},
});
});
});
Expand Up @@ -221,12 +221,12 @@ exports[`embedded entities in mongo diffing 2`] = `
"function(entity, data, factory, newEntity, convertCustomTypes, schema) {
if (typeof data._id !== 'undefined') entity._id = data._id;
if (typeof data.name !== 'undefined') entity.name = data.name;
if (data.profile1_username !== undefined || data.profile1_identity !== undefined || data.profile1_source !== undefined) {
if (data.profile1_username !== undefined || data.profile1_identity_email !== undefined || data.profile1_identity_meta_foo != null || data.profile1_identity_meta_bar != null || data.profile1_identity_meta_source != null || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined || data.profile1_source !== undefined) {
if (entity.profile1 == null) {
entity.profile1 = factory.createEmbeddable('Profile', data, { newEntity, convertCustomTypes });
}
if (typeof data.profile1_username !== 'undefined') entity.profile1.username = data.profile1_username;
if (data.profile1_identity_email !== undefined || data.profile1_identity_meta !== undefined || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined) {
if (data.profile1_identity_email !== undefined || data.profile1_identity_meta_foo != null || data.profile1_identity_meta_bar != null || data.profile1_identity_meta_source != null || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined) {
if (entity.profile1.identity == null) {
entity.profile1.identity = factory.createEmbeddable('Identity', data, { newEntity, convertCustomTypes });
}
Expand Down Expand Up @@ -479,7 +479,7 @@ exports[`embedded entities in mongo diffing 2`] = `
entity.profile1 = factory.createEmbeddable('Profile', data.profile1, { newEntity, convertCustomTypes });
}
if (data.profile1 && typeof data.profile1.username !== 'undefined') entity.profile1.username = data.profile1.username;
if (data.profile1_identity_email !== undefined || data.profile1_identity_meta !== undefined || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined) {
if (data.profile1_identity_email !== undefined || data.profile1_identity_meta_foo != null || data.profile1_identity_meta_bar != null || data.profile1_identity_meta_source != null || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined) {
if (entity.profile1.identity == null) {
entity.profile1.identity = factory.createEmbeddable('Identity', data, { newEntity, convertCustomTypes });
}
Expand Down
Expand Up @@ -221,12 +221,12 @@ exports[`embedded entities in postgres diffing 2`] = `
"function(entity, data, factory, newEntity, convertCustomTypes, schema) {
if (typeof data.id !== 'undefined') entity.id = data.id;
if (typeof data.name !== 'undefined') entity.name = data.name;
if (data.profile1_username !== undefined || data.profile1_identity !== undefined || data.profile1_source !== undefined) {
if (data.profile1_username !== undefined || data.profile1_identity_email !== undefined || data.profile1_identity_meta_foo != null || data.profile1_identity_meta_bar != null || data.profile1_identity_meta_source != null || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined || data.profile1_source !== undefined) {
if (entity.profile1 == null) {
entity.profile1 = factory.createEmbeddable('Profile', data, { newEntity, convertCustomTypes });
}
if (typeof data.profile1_username !== 'undefined') entity.profile1.username = data.profile1_username;
if (data.profile1_identity_email !== undefined || data.profile1_identity_meta !== undefined || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined) {
if (data.profile1_identity_email !== undefined || data.profile1_identity_meta_foo != null || data.profile1_identity_meta_bar != null || data.profile1_identity_meta_source != null || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined) {
if (entity.profile1.identity == null) {
entity.profile1.identity = factory.createEmbeddable('Identity', data, { newEntity, convertCustomTypes });
}
Expand Down Expand Up @@ -479,7 +479,7 @@ exports[`embedded entities in postgres diffing 2`] = `
entity.profile1 = factory.createEmbeddable('Profile', data.profile1, { newEntity, convertCustomTypes });
}
if (data.profile1 && typeof data.profile1.username !== 'undefined') entity.profile1.username = data.profile1.username;
if (data.profile1_identity_email !== undefined || data.profile1_identity_meta !== undefined || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined) {
if (data.profile1_identity_email !== undefined || data.profile1_identity_meta_foo != null || data.profile1_identity_meta_bar != null || data.profile1_identity_meta_source != null || data.profile1_identity_links !== undefined || data.profile1_identity_source !== undefined) {
if (entity.profile1.identity == null) {
entity.profile1.identity = factory.createEmbeddable('Identity', data, { newEntity, convertCustomTypes });
}
Expand Down

0 comments on commit 2bbcb47

Please sign in to comment.