Skip to content

Commit

Permalink
fix(core): improve handling of nullable embedded properties
Browse files Browse the repository at this point in the history
Closes #4787
  • Loading branch information
B4nan committed Oct 5, 2023
1 parent 2f58556 commit eae7e38
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 21 deletions.
16 changes: 11 additions & 5 deletions packages/core/src/hydration/ObjectHydrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,18 +222,18 @@ export class ObjectHydrator extends Hydrator {
}
};

const createCond = (prop: EntityProperty, dataKey: string) => {
const createCond = (prop: EntityProperty, dataKey: string, cond?: string) => {
const conds: string[] = [];

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

Expand Down Expand Up @@ -278,7 +278,13 @@ export class ObjectHydrator extends Hydrator {

/* istanbul ignore next */
const nullVal = this.config.get('forceUndefined') ? 'undefined' : 'null';
ret.push(` } else if (data${dataKey} === null) {`);

if (prop.object) {
ret.push(` } else if (data${dataKey} === null) {`);
} else {
ret.push(` } else if (${createCond(prop, dataKey, '=== null').join(' && ')}) {`);
}

ret.push(` entity${entityKey} = ${nullVal};`);
ret.push(` }`);

Expand Down
11 changes: 10 additions & 1 deletion packages/knex/src/query/CriteriaNodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,21 @@ export class CriteriaNodeFactory {

static createObjectItemNode(metadata: MetadataStorage, entityName: string, node: ICriteriaNode, payload: Dictionary, item: string, meta?: EntityMetadata) {
const prop = meta?.properties[item];
const childEntity = prop && prop.reference !== ReferenceType.SCALAR ? prop.type : entityName;

if (prop?.reference !== ReferenceType.EMBEDDED) {
const childEntity = prop && prop.reference !== ReferenceType.SCALAR ? prop.type : entityName;
return this.createNode(metadata, childEntity, payload[item], node, item);
}

if (payload[item] == null) {
const map = Object.keys(prop.embeddedProps).reduce((oo, k) => {
oo[prop.embeddedProps[k].name] = null;
return oo;
}, {});

return this.createNode(metadata, entityName, map, node, item);
}

const operator = Object.keys(payload[item]).some(f => Utils.isOperator(f));

if (operator) {
Expand Down
53 changes: 53 additions & 0 deletions tests/features/embeddables/GH4787.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Embeddable, Embedded, Entity, PrimaryKey, Property } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';

@Embeddable()
class A {

@Property()
foo!: string;

@Property()
bar!: number;

}

@Entity()
class B {

@PrimaryKey()
id!: number;

@Embedded({ entity: () => A, object: false, nullable: true })
a!: A | null;

}

let orm: MikroORM;

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

orm.em.create(B, { a: null });
await orm.em.flush();
orm.em.clear();
});

afterAll(() => orm.close(true));

test('embedded should be null', async () => {
const [b] = await orm.em.find(B, {});
expect(b.a).toBeNull(); // undefined
});

test('get embedded by null', async () => {
const a = await orm.em.findOne(B, { a: { foo: null } });
expect(a).not.toBeNull();

const f = await orm.em.findOne(B, { a: null }); // throws
expect(f).not.toBeNull();
});
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ exports[`embedded entities in mongo diffing 2`] = `
entity.profile1.identity.meta.source = factory.create('Source', data.profile1_identity_meta_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1_identity_meta === null) {
} else if (data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null) {
entity.profile1.identity.meta = null;
}
if (data.profile1_identity_meta != null) {
Expand Down Expand Up @@ -341,7 +341,7 @@ exports[`embedded entities in mongo diffing 2`] = `
entity.profile1.identity.source = factory.create('Source', data.profile1_identity_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1_identity === null) {
} else if (data.profile1_identity_email === null && data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null && data.profile1_identity_links === null && data.profile1_identity_source === null) {
entity.profile1.identity = null;
}
if (data.profile1_identity != null) {
Expand All @@ -364,7 +364,7 @@ exports[`embedded entities in mongo diffing 2`] = `
entity.profile1.identity.meta.source = factory.create('Source', data.profile1_identity_meta_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1_identity.meta === null) {
} else if (data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null) {
entity.profile1.identity.meta = null;
}
if (data.profile1_identity.meta != null) {
Expand Down Expand Up @@ -471,7 +471,7 @@ exports[`embedded entities in mongo diffing 2`] = `
entity.profile1.source = factory.create('Source', data.profile1_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1 === null) {
} else if (data.profile1_username === null && data.profile1_identity_email === null && data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null && data.profile1_identity_links === null && data.profile1_identity_source === null && data.profile1_source === null) {
entity.profile1 = null;
}
if (data.profile1 != null) {
Expand Down Expand Up @@ -499,7 +499,7 @@ exports[`embedded entities in mongo diffing 2`] = `
entity.profile1.identity.meta.source = factory.create('Source', data.profile1_identity_meta_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1_identity_meta === null) {
} else if (data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null) {
entity.profile1.identity.meta = null;
}
if (data.profile1_identity_meta != null) {
Expand Down Expand Up @@ -594,7 +594,7 @@ exports[`embedded entities in mongo diffing 2`] = `
entity.profile1.identity.source = factory.create('Source', data.profile1_identity_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1.identity === null) {
} else if (data.profile1_identity_email === null && data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null && data.profile1_identity_links === null && data.profile1_identity_source === null) {
entity.profile1.identity = null;
}
if (data.profile1.identity != null) {
Expand All @@ -617,7 +617,7 @@ exports[`embedded entities in mongo diffing 2`] = `
entity.profile1.identity.meta.source = factory.create('Source', data.profile1_identity_meta_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1.identity.meta === null) {
} else if (data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null) {
entity.profile1.identity.meta = null;
}
if (data.profile1.identity.meta != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ exports[`embedded entities in postgres diffing 2`] = `
entity.profile1.identity.meta.source = factory.create('Source', data.profile1_identity_meta_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1_identity_meta === null) {
} else if (data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null) {
entity.profile1.identity.meta = null;
}
if (data.profile1_identity_meta != null) {
Expand Down Expand Up @@ -341,7 +341,7 @@ exports[`embedded entities in postgres diffing 2`] = `
entity.profile1.identity.source = factory.create('Source', data.profile1_identity_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1_identity === null) {
} else if (data.profile1_identity_email === null && data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null && data.profile1_identity_links === null && data.profile1_identity_source === null) {
entity.profile1.identity = null;
}
if (data.profile1_identity != null) {
Expand All @@ -364,7 +364,7 @@ exports[`embedded entities in postgres diffing 2`] = `
entity.profile1.identity.meta.source = factory.create('Source', data.profile1_identity_meta_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1_identity.meta === null) {
} else if (data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null) {
entity.profile1.identity.meta = null;
}
if (data.profile1_identity.meta != null) {
Expand Down Expand Up @@ -471,7 +471,7 @@ exports[`embedded entities in postgres diffing 2`] = `
entity.profile1.source = factory.create('Source', data.profile1_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1 === null) {
} else if (data.profile1_username === null && data.profile1_identity_email === null && data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null && data.profile1_identity_links === null && data.profile1_identity_source === null && data.profile1_source === null) {
entity.profile1 = null;
}
if (data.profile1 != null) {
Expand Down Expand Up @@ -499,7 +499,7 @@ exports[`embedded entities in postgres diffing 2`] = `
entity.profile1.identity.meta.source = factory.create('Source', data.profile1_identity_meta_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1_identity_meta === null) {
} else if (data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null) {
entity.profile1.identity.meta = null;
}
if (data.profile1_identity_meta != null) {
Expand Down Expand Up @@ -594,7 +594,7 @@ exports[`embedded entities in postgres diffing 2`] = `
entity.profile1.identity.source = factory.create('Source', data.profile1_identity_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1.identity === null) {
} else if (data.profile1_identity_email === null && data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null && data.profile1_identity_links === null && data.profile1_identity_source === null) {
entity.profile1.identity = null;
}
if (data.profile1.identity != null) {
Expand All @@ -617,7 +617,7 @@ exports[`embedded entities in postgres diffing 2`] = `
entity.profile1.identity.meta.source = factory.create('Source', data.profile1_identity_meta_source, { initialized: true, merge: true, newEntity, convertCustomTypes, schema });
}
}
} else if (data.profile1.identity.meta === null) {
} else if (data.profile1_identity_meta_foo === null && data.profile1_identity_meta_bar === null && data.profile1_identity_meta_source === null) {
entity.profile1.identity.meta = null;
}
if (data.profile1.identity.meta != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exports[`polymorphic embeddables in sqlite diffing 1`] = `
if (typeof data.pet_type !== 'undefined') entity.pet.type = data.pet_type;
if (typeof data.pet_name !== 'undefined') entity.pet.name = data.pet_name;
if (typeof data.pet_canMeow !== 'undefined') entity.pet.canMeow = data.pet_canMeow === null ? null : !!data.pet_canMeow;
} else if (data.pet === null) {
} else if (data.pet_canBark === null && data.pet_type === null && data.pet_name === null && data.pet_canMeow === null) {
entity.pet = null;
}
if (typeof data.pet === 'string') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ describe('embedded entities in postgres', () => {
{ bar: 'b4', foo: 'f4' },
] },
],
meta: null,
},
});
expect(u2.profile2).toBeInstanceOf(Profile);
Expand Down

0 comments on commit eae7e38

Please sign in to comment.