-
-
Notifications
You must be signed in to change notification settings - Fork 502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ManyToOne in composite primary key does not work if referenced entity also uses a composite key #2647
Comments
Please provide reproduction as code, ideally as a test case. Entity definition looks good, but I can easily imagine you oversimplified it. Also have you tried it without What does
The PKs are
That sounds a bit wrong, maybe that's where the bug is, |
I will check, but I don't think changing it has any effect.
I'm sorry, I should have written 4.5.9.
I apologize for the confusion. by I'll amend my issue with an example test case shortly. |
I'm running into the same issue in 5.0.0-dev.645, and I'm working on a more comprehensive test case to illustrate the issue. As part of my investigation, I'm also finding that there's an issue in
Here, the composite key contains three values: [
'933569619573211136', // the value of `cab.c.id` in entity (`c_id` in table)
'874812707751215184', // the value of `cab.ab.a.id` in entity (`ab_a_id` in table)
'846302692724047893' // the value of `cab.ab.b.id` in entity (`ab_b_id` in table)
] In constructor, key is
When the query is constructed, there doesn't seem to be any reference to |
Not reproducible, here is a passing test case with entity definition you provided: import { Entity, ManyToOne, MikroORM, PrimaryKey } from '@mikro-orm/core';
@Entity()
export class A {
@PrimaryKey()
id!: number;
}
@Entity()
export class B {
@PrimaryKey()
id!: number;
}
@Entity()
export class C {
@PrimaryKey()
id!: number;
}
@Entity()
export class AB {
@ManyToOne(() => A, { primary: true })
a!: A;
@ManyToOne(() => B, { primary: true })
b!: B;
}
@Entity()
export class CAB {
@ManyToOne(() => C, { primary: true })
c!: C;
@ManyToOne(() => AB, { primary: true })
ab!: AB;
}
describe('GH #2647', () => {
let orm: MikroORM;
beforeAll(async () => {
orm = await MikroORM.init({
entities: [A, B, C, AB, CAB],
dbName: `:memory:`,
type: 'sqlite',
});
await orm.getSchemaGenerator().createSchema();
});
afterAll(async () => {
await orm.close(true);
});
it('working with complex composite entities', async () => {
const a = new A();
const b = new B();
const c = new C();
const ab = new AB();
ab.a = a;
ab.b = b;
await orm.em.persistAndFlush([ab, c]);
const cab = new CAB();
cab.ab = ab;
cab.c = c;
await orm.em.persistAndFlush(cab);
orm.em.clear();
const res = await orm.em.find(CAB, {}, { populate: true });
expect(res).toHaveLength(1);
});
}); So please instead of more investigation, provide actual reproduction, that is always the first step. I am also waiting for repro of the ESM issue - that should really be a one minute thing for you, I just need to see how the stack trace look like. |
Closing as not reproducible, will reopen if you provide failing repro. |
I was able to repro this with this test: import { Entity, ManyToOne, MikroORM, PrimaryKey } from '@mikro-orm/core';
@Entity()
export class A {
@PrimaryKey()
id: number;
constructor(id: number) {
this.id = id;
}
}
@Entity()
export class B {
@PrimaryKey()
id: number;
constructor(id: number) {
this.id = id;
}
}
@Entity()
export class C {
@PrimaryKey()
id: number;
constructor(id: number) {
this.id = id;
}
}
@Entity()
export class D {
@PrimaryKey()
id: number;
constructor(id: number) {
this.id = id;
}
}
@Entity()
export class AB {
@ManyToOne(() => A, { eager: true, primary: true })
a: A;
@ManyToOne(() => B, { eager: true, primary: true })
b: B;
constructor(a: A, b: B) {
this.a = a;
this.b = b;
}
}
@Entity()
export class CAB {
@ManyToOne(() => C, { eager: true, primary: true })
c: C;
@ManyToOne(() => AB, { eager: true, primary: true })
ab: AB;
constructor(c: C, ab: AB) {
this.c = c;
this.ab = ab;
}
}
@Entity()
export class DCAB {
@ManyToOne(() => D, { eager: true, primary: true })
d: D;
@ManyToOne(() => CAB, { eager: true, primary: true })
cab: CAB;
constructor(d: D, cab: CAB) {
this.d = d;
this.cab = cab;
}
}
describe('GH #2647', () => {
let orm: MikroORM;
beforeAll(async () => {
orm = await MikroORM.init({
entities: [A, B, C, D, AB, CAB, DCAB],
dbName: `:memory:`,
type: 'sqlite'
});
await orm.getSchemaGenerator().createSchema();
});
afterAll(async () => {
await orm.close(true);
});
it('should be able to find entity with nested composite key', async () => {
await orm.em.nativeDelete(DCAB, {});
await orm.em.nativeDelete(CAB, {});
await orm.em.nativeDelete(AB, {});
await orm.em.nativeDelete(D, {});
await orm.em.nativeDelete(C, {});
await orm.em.nativeDelete(B, {});
await orm.em.nativeDelete(A, {});
const a = new A(1);
const b = new B(2);
const c = new C(3);
const d = new D(4);
const ab = new AB(a, b);
const cab = new CAB(c, ab);
const dcab = new DCAB(d, cab);
await orm.em.persistAndFlush([a, b, c, d, ab, cab, dcab]);
orm.em.clear();
const res = await orm.em.find(DCAB, { d, cab });
expect(res).toHaveLength(1);
});
}); |
Thanks, will look into! |
Oh damn, this is very complex, every issue I fix leads to something else. |
I'm not quite sure this is fully resolved, as I'm getting the following error in DriverException: Expected 3 bindings, saw 2
at SqliteExceptionConverter.convertException (C:\dev\project\node_modules\@mikro-orm\core\dist\platforms\ExceptionConverter.js:8:16)
at SqliteExceptionConverter.convertException (C:\dev\project\node_modules\@mikro-orm\sqlite\dist\SqliteExceptionConverter.js:46:22)
at SqliteDriver.convertException (C:\dev\project\node_modules\@mikro-orm\core\dist\drivers\DatabaseDriver.js:180:54)
at C:\dev\project\node_modules\@mikro-orm\core\dist\drivers\DatabaseDriver.js:184:24
at async SqliteDriver.find (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\@mikro-orm\knex\dist\AbstractSqlDriver.js:46:24)
at async SqliteDriver.findOne (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\@mikro-orm\knex\dist\AbstractSqlDriver.js:60:21)
at async SqlEntityManager.findOne (C:\dev\project\node_modules\@mikro-orm\core\dist\EntityManager.js:282:22)
at async WrappedEntity.init (C:\dev\project\node_modules\@mikro-orm\core\dist\entity\WrappedEntity.js:55:9)
at async Reference.load (C:\dev\project\node_modules\@mikro-orm\core\dist\entity\Reference.js:66:13)
at SessionLogic._SessionLogic_updateDeviceControlMessage (C:\dev\project\src\logic\sessionLogic.ts:536:84)
previous Error: Expected 3 bindings, saw 2
at replaceRawArrBindings (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\formatter\rawFormatter.js:27:11)
at Raw.toSQL (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\raw.js:77:13)
at unwrapRaw (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\formatter\wrappingFormatter.js:116:19)
at Client_SQLite3.parameter (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\client.js:384:12)
at Client_SQLite3.values (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\dialects\sqlite3\index.js:219:23)
at QueryCompiler_SQLite3.whereIn (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\query\querycompiler.js:950:32)
at QueryCompiler_SQLite3.where (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\query\querycompiler.js:521:34)
at C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\query\querycompiler.js:127:69
at Array.map (<anonymous>)
at QueryCompiler_SQLite3.select (C:\dev\project\node_modules\@mikro-orm\sqlite\node_modules\knex\lib\query\querycompiler.js:127:35) If I set a breakpoint on
If this isn't showing up in the test for this, I'll see if I can put together a test that triggers this behavior. |
I stepped through into
|
Thanks for debugging, but I really need to see reproductions first. |
Repro (tested locally in the mikro-orm codebase): import { Entity, ManyToOne, MikroORM, PrimaryKey } from '@mikro-orm/core';
@Entity()
export class Provider {
@PrimaryKey()
id: number;
constructor(id: number) {
this.id = id;
}
}
@Entity()
export class User {
@PrimaryKey()
id: number;
constructor(id: number) {
this.id = id;
}
}
@Entity()
export class Member {
@ManyToOne(() => Provider, { eager: true, primary: true })
provider: Provider;
@ManyToOne(() => User, { eager: true, primary: true })
user: User;
constructor(a: Provider, b: User) {
this.provider = a;
this.user = b;
}
}
@Entity()
export class Session {
@PrimaryKey()
id: number;
@ManyToOne(() => Member, { eager: true })
owner: Member;
@ManyToOne(() => Participant, { nullable: true, default: null, eager: true })
lastActionBy: Participant | null = null;
constructor(id: number, owner: Member) {
this.id = id;
this.owner = owner;
}
}
@Entity()
export class Participant {
@ManyToOne(() => Session, { eager: true, primary: true })
session: Session;
@ManyToOne(() => Member, { eager: true, primary: true })
member: Member;
constructor(session: Session, member: Member) {
this.session = session;
this.member = member;
}
}
describe('GH #2647-2', () => {
let orm: MikroORM;
beforeAll(async () => {
orm = await MikroORM.init({
entities: [Provider, User, Member, Session, Participant],
dbName: `:memory:`,
type: 'sqlite',
// NOTE: this just shows the query is executed repeatedly
debug: true
});
await orm.getSchemaGenerator().createSchema();
});
afterAll(async () => {
await orm.close(true);
});
beforeEach(async () => {
await orm.em.nativeDelete(Participant, {});
await orm.em.nativeDelete(Member, {});
await orm.em.nativeDelete(Session, {});
await orm.em.nativeDelete(User, {});
await orm.em.nativeDelete(Provider, {});
});
function createEntities(pks: [providerId: number, userId: number, sessionId: number]) {
const provider = new Provider(pks[0]);
const user = new User(pks[1]);
const member = new Member(provider, user);
const session = new Session(pks[2], member);
const participant = new Participant(session, member);
session.lastActionBy = participant;
orm.em.persist([provider, user, member, session, participant]);
return { provider, user, member, session, participant };
}
it('should be able to populate circularity', async () => {
const { session, member } = createEntities([1, 2, 3]);
await orm.em.flush();
orm.em.clear();
// this results in infinite recursion until memory is exhausted
const res = await orm.em.find(Participant, { session, member });
expect(res).toHaveLength(1);
});
}); Running this test results in this output (because I passed
And continues until either:
|
Ok so composite keys with cycles from PKs to the owning entity, and eager loading. Now I am not surprised it's broken, that's like the worst combination I can imagine :] |
Describe the bug
An entity that includes a
@ManyToOne
property in its primary key cannot be hydrated if the many-to-one reference points to another entity that uses a composite key.Stack trace
To Reproduce
Steps to reproduce the behavior:
await em.find(CAB, {})
Expected behavior
Currently, metadata discovery will determine the primary keys of
CAB
are["c", "ab"]
. WhenEntityFactory..createReference
is called during hydration, it receives a composite key of something like["<c.id>", "<a.id>", "<b.id>"]
, which isn't properly handled byUtils.getPrimaryKeyCondFromArray
orUtils.getOrderedPrimaryKeys
.I would expect
createReference
to be more thorough about expanding composite keys in the same way that filters, joins, and inserts do, without resulting in an exception.Versions
The text was updated successfully, but these errors were encountered: