diff --git a/packages/core/src/utils/QueryHelper.ts b/packages/core/src/utils/QueryHelper.ts
index 2d09da8d64c7..179344ad30ee 100644
--- a/packages/core/src/utils/QueryHelper.ts
+++ b/packages/core/src/utils/QueryHelper.ts
@@ -1,7 +1,7 @@
import { Reference } from '../entity/Reference';
import { Utils } from './Utils';
import type { AnyEntity, Dictionary, EntityMetadata, EntityProperty, FilterDef, ObjectQuery, FilterQuery } from '../typings';
-import { ARRAY_OPERATORS, GroupOperator } from '../enums';
+import { ARRAY_OPERATORS, GroupOperator, ReferenceType } from '../enums';
import type { Platform } from '../platforms';
import type { MetadataStorage } from '../metadata/MetadataStorage';
import { JsonType } from '../types';
@@ -56,7 +56,17 @@ export class QueryHelper {
}
if (meta.primaryKeys.every(pk => pk in where) && Utils.getObjectKeysSize(where) === meta.primaryKeys.length) {
- return !GroupOperator[key as string] && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => !Utils.isOperator(v, false)));
+ return !GroupOperator[key as string] && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
+ if (Utils.isOperator(v, false)) {
+ return false;
+ }
+
+ if ([ReferenceType.ONE_TO_ONE, ReferenceType.MANY_TO_ONE].includes(meta.properties[k].reference)) {
+ return this.inlinePrimaryKeyObjects(where[k], meta.properties[k].targetMeta, metadata, v);
+ }
+
+ return true;
+ }));
}
Object.keys(where).forEach(k => {
diff --git a/packages/knex/src/query/ObjectCriteriaNode.ts b/packages/knex/src/query/ObjectCriteriaNode.ts
index f423c4ecf0b0..7beaa852538b 100644
--- a/packages/knex/src/query/ObjectCriteriaNode.ts
+++ b/packages/knex/src/query/ObjectCriteriaNode.ts
@@ -105,7 +105,16 @@ export class ObjectCriteriaNode extends CriteriaNode {
const embeddable = this.prop.reference === ReferenceType.EMBEDDED;
const knownKey = [ReferenceType.SCALAR, ReferenceType.MANY_TO_ONE, ReferenceType.EMBEDDED].includes(this.prop.reference) || (this.prop.reference === ReferenceType.ONE_TO_ONE && this.prop.owner);
const operatorKeys = knownKey && Object.keys(this.payload).every(key => Utils.isOperator(key, false));
- const primaryKeys = knownKey && Object.keys(this.payload).every(key => this.metadata.find(this.entityName)!.primaryKeys.includes(key));
+ const primaryKeys = knownKey && Object.keys(this.payload).every(key => {
+ const meta = this.metadata.find(this.entityName)!;
+ if (!meta.primaryKeys.includes(key)) {
+ return false;
+ }
+ if (!Utils.isPlainObject(this.payload[key].payload) || ![ReferenceType.ONE_TO_ONE, ReferenceType.MANY_TO_ONE].includes(meta.properties[key].reference)) {
+ return true;
+ }
+ return Object.keys(this.payload[key].payload).every(k => meta.properties[key].targetMeta!.primaryKeys.includes(k));
+ });
return !primaryKeys && !nestedAlias && !operatorKeys && !embeddable;
}
diff --git a/tests/issues/GH2648.test.ts b/tests/issues/GH2648.test.ts
new file mode 100644
index 000000000000..1d637b1bde0c
--- /dev/null
+++ b/tests/issues/GH2648.test.ts
@@ -0,0 +1,130 @@
+import { Entity, IdentifiedReference, JsonType, ManyToOne, MikroORM, OneToOne, PrimaryKey, PrimaryKeyType, Property } from '@mikro-orm/core';
+import type { SqliteDriver } from '@mikro-orm/sqlite';
+
+@Entity()
+export class A {
+
+ @PrimaryKey()
+ id!: number;
+
+ @Property({ type: 'string' })
+ test!: string;
+
+}
+
+@Entity()
+export class B1 {
+
+ [PrimaryKeyType]: number;
+ @ManyToOne({ entity: () => A, primary: true, wrappedReference: true })
+ a!: IdentifiedReference;
+
+}
+
+@Entity()
+export class B2 {
+
+ @PrimaryKey()
+ id!: number;
+
+ [PrimaryKeyType]: number;
+ @OneToOne({ entity: () => A, primary: true, wrappedReference: true })
+ a!: IdentifiedReference;
+
+}
+
+@Entity()
+export class B3 {
+
+ [PrimaryKeyType]: number;
+ @OneToOne({ entity: () => A, primary: true, wrappedReference: true })
+ a!: IdentifiedReference;
+
+}
+
+@Entity()
+export class B4 {
+
+ @PrimaryKey()
+ id!: number;
+
+ [PrimaryKeyType]: number;
+ @OneToOne({ entity: () => A, primary: true, wrappedReference: true })
+ a!: IdentifiedReference;
+
+}
+
+@Entity()
+export class C {
+
+ @PrimaryKey({ type: Number })
+ id!: number;
+
+ @ManyToOne({ entity: () => B1, wrappedReference: true })
+ b1!: IdentifiedReference;
+
+ @ManyToOne({ entity: () => B2, wrappedReference: true })
+ b2!: IdentifiedReference;
+
+ @ManyToOne({ entity: () => B3, wrappedReference: true })
+ b3!: IdentifiedReference;
+
+ @ManyToOne({ entity: () => B4, wrappedReference: true })
+ b4!: IdentifiedReference;
+
+}
+
+interface Test {
+ t1: string;
+ t2: string;
+}
+
+@Entity()
+export class D {
+
+ @PrimaryKey({ type: JsonType })
+ id!: Test;
+
+}
+
+describe('GH issue 2648', () => {
+
+ let orm: MikroORM;
+
+ beforeAll(async () => {
+ orm = await MikroORM.init({
+ entities: [A, B1, B2, B3, B4, C, D],
+ dbName: ':memory:',
+ type: 'sqlite',
+ });
+ await orm.getSchemaGenerator().createSchema();
+ });
+
+ afterAll(() => orm.close(true));
+
+ test('JSON as pk', async () => {
+ const r = await orm.em.findOne(D, { id: { t1: 'a', t2: 'b' } });
+ expect(r).toBeNull();
+ });
+
+ test('fk as pk with ManyToOne', async () => {
+ const r = await orm.em.findOne(C, { b1: { a: { test: 'test' } } });
+ expect(r).toBeNull();
+ });
+
+ test('fk as pk with ManyToOne and with additional primary key', async () => {
+ const r = await orm.em.findOne(C, { b2: { a: { test: 'test' } } });
+ expect(r).toBeNull();
+ });
+
+ test('fk as pk with OneToOne', async () => {
+ const r = await orm.em.findOne(C, { b3: { a: { test: 'test' } } });
+ expect(r).toBeNull();
+ });
+
+ test('fk as pk with OneToOne and with additional primary key', async () => {
+ const r = await orm.em.findOne(C, { b4: { a: { test: 'test' } } });
+ expect(r).toBeNull();
+ });
+
+});