Skip to content

Commit

Permalink
fix(core): fix hydration of object embeddables via joined strategy
Browse files Browse the repository at this point in the history
Closes #5020
  • Loading branch information
B4nan committed Dec 16, 2023
1 parent 1ca6831 commit b3e3e55
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 15 deletions.
38 changes: 25 additions & 13 deletions packages/knex/src/AbstractSqlDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
type NativeInsertUpdateOptions,
type Options,
type OrderDefinition,
parseJsonSafe,
type PopulateOptions,
type Primary,
QueryFlag,
Expand Down Expand Up @@ -349,32 +350,43 @@ export abstract class AbstractSqlDriver<Connection extends AbstractSqlConnection
}
});

const props = ref
const targetProps = ref
? meta2.getPrimaryProps()
: meta2.props.filter(prop => this.platform.shouldHaveColumn(prop, hint.children as any || []));

for (const prop1 of props) {
if (prop1.fieldNames.length > 1) { // composite keys
const fk = prop1.fieldNames.map(name => root![`${relationAlias}__${name}` as EntityKey<T>]) as Primary<T>[];
relationPojo[prop1.name] = Utils.mapFlatCompositePrimaryKey(fk, prop1) as EntityValue<T>;
} else if (prop1.runtimeType === 'Date') {
const alias = `${relationAlias}__${prop1.fieldNames[0]}` as EntityKey<T>;
relationPojo[prop1.name] = (typeof root![alias] === 'string' ? new Date(root![alias] as string) : root![alias]) as EntityValue<T>;
for (const prop of targetProps) {
if (prop.fieldNames.length > 1) { // composite keys
const fk = prop.fieldNames.map(name => root![`${relationAlias}__${name}` as EntityKey<T>]) as Primary<T>[];
relationPojo[prop.name] = Utils.mapFlatCompositePrimaryKey(fk, prop) as EntityValue<T>;
} else if (prop.runtimeType === 'Date') {
const alias = `${relationAlias}__${prop.fieldNames[0]}` as EntityKey<T>;
relationPojo[prop.name] = (typeof root![alias] === 'string' ? new Date(root![alias] as string) : root![alias]) as EntityValue<T>;
} else {
const alias = `${relationAlias}__${prop1.fieldNames[0]}` as EntityKey<T>;
relationPojo[prop1.name] = root![alias];
const alias = `${relationAlias}__${prop.fieldNames[0]}` as EntityKey<T>;
relationPojo[prop.name] = root![alias];

if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
const item = parseJsonSafe(relationPojo[prop.name]);

if (Array.isArray(item)) {
relationPojo[prop.name] = item.map(row => row == null ? row : this.comparator.mapResult(prop.type, row)) as EntityValue<T>;
} else {
relationPojo[prop.name] = item == null ? item : this.comparator.mapResult(prop.type, item) as EntityValue<T>;
}
}
}
}

// properties can be mapped to multiple places, e.g. when sharing a column in multiple FKs,
// so we need to delete them after everything is mapped from given level
for (const prop1 of props) {
prop1.fieldNames.map(name => delete root![`${relationAlias}__${name}` as EntityKey<T>]);
for (const prop of targetProps) {
prop.fieldNames.map(name => delete root![`${relationAlias}__${name}` as EntityKey<T>]);
}

if (ref) {
const tmp = Object.values(relationPojo);
relationPojo = meta2.compositePK ? tmp : tmp[0] as any;
/* istanbul ignore next */
relationPojo = (meta2.compositePK ? tmp : tmp[0]) as EntityData<T>;
}

if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
Expand Down
104 changes: 104 additions & 0 deletions tests/features/embeddables/GH5020.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { Embeddable, Embedded, Entity, ManyToOne, MikroORM, PrimaryKey, Property, Ref, types } from '@mikro-orm/sqlite';

@Embeddable()
class Settings {

@Property({ type: types.string })
name!: string;

@Property({ type: types.double })
memberCount!: number;

@Property({ type: types.boolean })
isActive!: boolean;

constructor(name: string, memberCount: number, isActive: boolean) {
this.name = name;
this.memberCount = memberCount;
this.isActive = isActive;
}

}

@Entity()
class Organization {

@PrimaryKey()
id!: number;

@Embedded({ entity: () => Settings, object: true })
settings!: Settings;

constructor(settings: Settings) {
this.settings = settings;
}

}

@Entity()
class User {

@PrimaryKey()
id!: number;

@Property()
name: string;

@Property({ unique: true })
email: string;

@ManyToOne({ entity: () => Organization, ref: true })
organization!: Ref<Organization>;

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

}

let orm: MikroORM;

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

orm.em.create(User, {
name: 'Foo',
email: 'foo',
organization: orm.em.create(Organization, {
settings: new Settings('Bar', 9000, false),
}),
});
await orm.em.flush();
orm.em.clear();
});

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

test('joined strategy and object embeddables with not matching field names', async () => {
const user = await orm.em.findOneOrFail(
User,
{ email: 'foo' },
{ populate: ['organization'] },
);
const relatedOrganization = user.organization.getEntity();
expect(user.name).toBe('Foo');
expect(relatedOrganization.settings.name).toBe('Bar');
expect(relatedOrganization.settings.memberCount).toBe(9000); // This fails
expect(relatedOrganization.settings.isActive).toBe(false); // This fails too
});

test('simple find and object embeddables with not matching field names', async () => {
const organization = await orm.em.findOneOrFail(Organization, {
id: 1,
});
expect(organization.settings.name).toBe('Bar');
expect(organization.settings.memberCount).toBe(9000);
expect(organization.settings.isActive).toBe(false);
});
4 changes: 2 additions & 2 deletions tests/issues/GH4227.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Entity, LoadStrategy, OneToMany, ManyToOne, Collection, PrimaryKey, BigIntType } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/postgresql';
import { Entity, LoadStrategy, OneToMany, ManyToOne, Collection, PrimaryKey } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';

@Entity()
class BidEntity {
Expand Down

0 comments on commit b3e3e55

Please sign in to comment.