Skip to content

Commit

Permalink
fix(core): fix nested query with fk as pk (#2650)
Browse files Browse the repository at this point in the history
Closes #2648

Co-authored-by: Marcel Link <marcel.link@paar-it.de>
  • Loading branch information
ml1nk and Marcel Link committed Jan 20, 2022
1 parent 02e2278 commit cc54ff9
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 3 deletions.
14 changes: 12 additions & 2 deletions packages/core/src/utils/QueryHelper.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 => {
Expand Down
11 changes: 10 additions & 1 deletion packages/knex/src/query/ObjectCriteriaNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
130 changes: 130 additions & 0 deletions tests/issues/GH2648.test.ts
Original file line number Diff line number Diff line change
@@ -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<A>;

}

@Entity()
export class B2 {

@PrimaryKey()
id!: number;

[PrimaryKeyType]: number;
@OneToOne({ entity: () => A, primary: true, wrappedReference: true })
a!: IdentifiedReference<A>;

}

@Entity()
export class B3 {

[PrimaryKeyType]: number;
@OneToOne({ entity: () => A, primary: true, wrappedReference: true })
a!: IdentifiedReference<A>;

}

@Entity()
export class B4 {

@PrimaryKey()
id!: number;

[PrimaryKeyType]: number;
@OneToOne({ entity: () => A, primary: true, wrappedReference: true })
a!: IdentifiedReference<A>;

}

@Entity()
export class C {

@PrimaryKey({ type: Number })
id!: number;

@ManyToOne({ entity: () => B1, wrappedReference: true })
b1!: IdentifiedReference<B1>;

@ManyToOne({ entity: () => B2, wrappedReference: true })
b2!: IdentifiedReference<B2>;

@ManyToOne({ entity: () => B3, wrappedReference: true })
b3!: IdentifiedReference<B3>;

@ManyToOne({ entity: () => B4, wrappedReference: true })
b4!: IdentifiedReference<B4>;

}

interface Test {
t1: string;
t2: string;
}

@Entity()
export class D {

@PrimaryKey({ type: JsonType })
id!: Test;

}

describe('GH issue 2648', () => {

let orm: MikroORM<SqliteDriver>;

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();
});

});

0 comments on commit cc54ff9

Please sign in to comment.