Skip to content

Commit

Permalink
fix(core): fix auto-refresh detection in em.find for inlined embedd…
Browse files Browse the repository at this point in the history
…ed properties

Closes #4904
  • Loading branch information
B4nan committed Nov 8, 2023
1 parent 6d08ea1 commit 759b7b8
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 56 deletions.
5 changes: 4 additions & 1 deletion packages/core/src/EntityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1789,7 +1789,10 @@ export class EntityManager<D extends IDatabaseDriver = IDatabaseDriver> {
if (options.fields) {
autoRefresh = options.fields.some(field => !helper(entity).__loadedProperties.has(field as string));
} else {
autoRefresh = meta.comparableProps.some(prop => !prop.lazy && !helper(entity).__loadedProperties.has(prop.name));
autoRefresh = meta.comparableProps.some(prop => {
const inlineEmbedded = prop.reference === ReferenceType.EMBEDDED && !prop.object;
return !inlineEmbedded && !prop.lazy && !helper(entity).__loadedProperties.has(prop.name);
});
}

if (autoRefresh) {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/hydration/ObjectHydrator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ export class ObjectHydrator extends Hydrator {
}

private safeKey(key: string): string {
return key.replace(/[^\w]/g, '_');
return key.replace(/\W/g, '_');
}

}
2 changes: 1 addition & 1 deletion packages/core/src/utils/EntityComparator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ export class EntityComparator {
}

private safeKey(key: string): string {
return key.replace(/[^\w]/g, '_');
return key.replace(/\W/g, '_');
}

/**
Expand Down
61 changes: 25 additions & 36 deletions tests/features/embeddables/GH3134.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import {
Embeddable,
Embedded,
Entity,
MikroORM,
PrimaryKey,
Property,
} from '@mikro-orm/core';
import { SqliteDriver } from '@mikro-orm/sqlite';
import { Embeddable, Embedded, Entity, PrimaryKey, Property } from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/sqlite';

@Embeddable()
class Nested {
Expand Down Expand Up @@ -50,38 +43,34 @@ beforeAll(async () => {
orm = await MikroORM.init({
entities: [Parent],
dbName: ':memory:',
driver: SqliteDriver,
});
await orm.schema.refreshDatabase();
await orm.schema.createSchema();
});

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

describe('Github issue 3134', () => {
test('load embedded entity twice', async () => {
// initial data
const nested = new Nested('A', 'B', 'C');
const parent = new Parent(1, nested);
await orm.em.fork().persistAndFlush(parent);

const em1 = orm.em.fork();
const p1 = await em1.findOneOrFail(Parent, 1);
expect(p1.nested.field1).toBe('A');
expect(p1.nested.field2).toBe('B');
expect(p1.nested.field3).toBe('C');

const em2 = orm.em.fork();
const p = await em2.findOneOrFail(Parent, 1);
p.nested.field1 = 'Z';
await em2.persistAndFlush(p);

const p2 = await em1.findOneOrFail(Parent, 1);
expect(p1).toBe(p2);
expect(p1.nested).toEqual({
field1: 'Z',
field2: 'B',
field3: 'C',
});
test('load embedded entity twice (GH #3134)', async () => {
// initial data
const nested = new Nested('A', 'B', 'C');
const parent = new Parent(1, nested);
await orm.em.fork().persistAndFlush(parent);

const em1 = orm.em.fork();
const p1 = await em1.findOneOrFail(Parent, 1);
expect(p1.nested.field1).toBe('A');
expect(p1.nested.field2).toBe('B');
expect(p1.nested.field3).toBe('C');

const em2 = orm.em.fork();
const p = await em2.findOneOrFail(Parent, 1);
p.nested.field1 = 'Z';
await em2.persistAndFlush(p);

await em1.refresh(p1);
expect(p1.nested).toEqual({
field1: 'Z',
field2: 'B',
field3: 'C',
});
});

108 changes: 108 additions & 0 deletions tests/features/embeddables/GH4904.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { MikroORM } from '@mikro-orm/sqlite';
import { EntitySchema, ReferenceType } from '@mikro-orm/core';
import { mockLogger } from '../../helpers';

class TestEntity1 {

id: string;
customProp: CustomProp;

constructor(id: string, customProp: CustomProp) {
this.id = id;
this.customProp = customProp;
}

}

class TestEntity2 {

id: string;
customProp: CustomProp;

constructor(id: string, customProp: CustomProp) {
this.id = id;
this.customProp = customProp;
}

}

class CustomProp {

someValue: string;

constructor(someValue: string) {
this.someValue = someValue;
}

}

const TestEntity1Schema = new EntitySchema({
class: TestEntity1,
properties: {
id: {
type: 'text',
primary: true,
},
customProp: {
type: 'CustomProp',
reference: ReferenceType.EMBEDDED,
object: true,
},
},
});

const TestEntity2Schema = new EntitySchema({
class: TestEntity2,
properties: {
id: {
type: 'text',
primary: true,
},
customProp: {
type: 'CustomProp',
reference: ReferenceType.EMBEDDED,
object: false,
},
},
});

const CustomPropSchema = new EntitySchema({
class: CustomProp,
embeddable: true,
properties: {
someValue: {
type: 'text',
},
},
});

let orm: MikroORM;

beforeAll(async () => {
orm = await MikroORM.init({
entities: [TestEntity1Schema, TestEntity2Schema, CustomPropSchema],
dbName: `:memory:`,
});

await orm.schema.createSchema();
});

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

test('preserve data fields that match pivot field', async () => {
const e1 = new TestEntity1('abc', new CustomProp('yyy'));
await orm.em.insert(TestEntity1, e1);

const e2 = new TestEntity2('def', new CustomProp('xxx'));
await orm.em.insert(TestEntity2, e2);

const mock = mockLogger(orm);

await orm.em.findOne(TestEntity1, 'abc');
await orm.em.findOne(TestEntity1, 'abc'); // should not trigger SQL query

await orm.em.findOne(TestEntity2, 'def');
await orm.em.findOne(TestEntity2, 'def'); // should not trigger SQL query

expect(mock).toBeCalledTimes(2);
});
24 changes: 7 additions & 17 deletions tests/features/embeddables/query-builder-embeddable.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import { BetterSqliteDriver } from '@mikro-orm/better-sqlite';
import {
Entity,
MikroORM,
PrimaryKey,
Embedded,
Embeddable,
Property,
} from '@mikro-orm/core';
import { MikroORM } from '@mikro-orm/better-sqlite';
import { Embeddable, Embedded, Entity, PrimaryKey, Property } from '@mikro-orm/core';

@Embeddable()
export class Settings {
class Settings {

@Property()
name: string;
Expand All @@ -21,7 +14,7 @@ export class Settings {
}

@Entity()
export class User {
class User {

@PrimaryKey()
id: number;
Expand All @@ -36,22 +29,19 @@ export class User {

}

let orm: MikroORM<BetterSqliteDriver>;
let orm: MikroORM;

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

const generator = orm.schema;
await generator.ensureDatabase();
await generator.dropSchema();
await generator.createSchema();
await orm.schema.createSchema();
});

afterAll(() => orm.close(true));
afterEach(() => orm.em.clear());

test('insert an object with embeddable using a QueryBuilder', async () => {
const foo = new User({ id: 1, settings: { name: 'foo' } });
Expand Down

0 comments on commit 759b7b8

Please sign in to comment.